
#include "etl_xml.h"

#include <stdio.h>
#include <malloc.h>
#include <string.h>

using namespace std; 
using namespace etl;

#ifdef WIN32
#else
#include <alloca.h>
#endif

etl_xml_node::etl_xml_node()
{
}

etl_xml_node::etl_xml_node(const etl_xml_node& obj)
{
    copy(obj);
}

int etl_xml::OpenAndSetDocument(etl_string fileNameAndPath)
{
    const int MAX_BUFFER_SIZE = 10000;
    char buffer[MAX_BUFFER_SIZE];
    ifstream file(fileNameAndPath.c_str());
    etl_string xmlDocumnet = "";

    if ( !file.is_open() )
        return -1;
    else
        while ( ! file.eof() )
        {
            file.getline(buffer,MAX_BUFFER_SIZE);
            xmlDocumnet += buffer;
        }

    return set_document( xmlDocumnet.c_str() );
}

etl_list<etl_xml_node*> etl_xml_node::get_matching_tags(etl_string tag)
{
    etl_list<etl_xml_node*> retList;
    etl_list<etl_xml_node*> children = get_children();
    for ( children.iter_begin();children.iter_valid();children.iter_next() )
        if ( tag == (*(children.iter_data()))->get_tag_name() )
            retList.append( (*(children.iter_data())) );
    return retList;
}

etl_string etl_xml_node::get_meta_data(etl_string name)
{
    etl_string ret_string;

    int name_len = name.length()+1;
    char* cmp_buf = (char*) alloca(name_len);
    memcpy( cmp_buf, name.c_str(), name_len );
    cmp_buf[name_len-1]='=';
    char* scan_buf = (char*)alloca( name_len );
    memset( scan_buf, 0, name_len );

    const char* pos = tag.c_str();
    bool found = false;

    while ( (pos < (tag.c_str() + tag.length())  && !found) )
    {
        char prevChar = 0;
        for ( int i = 0; i < (name_len - 1); i++ )
	{
            if ( i == 0 )
                prevChar = scan_buf[0];
            scan_buf[i] = scan_buf[i+1];
	}
        scan_buf[(name_len-1)] = *pos++;
        if ( strncmp( cmp_buf, scan_buf, name_len ) == 0 && (prevChar == CommaDelim || prevChar == SpaceDelim || prevChar == QuoteDelim) )
	{
            found = true;
            while ( *pos != '"' && (pos < (tag.c_str() + tag.length())) )
                pos++;
            const char* end = pos+1;
            while ( *end != '"' && (end < (tag.c_str() + tag.length())) )
                end++;

            if ( (pos < (tag.c_str() + tag.length())) && (end < (tag.c_str() + tag.length())) )
	    {
                int delta = (int)(end - pos);
                char* data = (char*)alloca( delta );
                memset( data, 0, delta );
                pos++;
                memcpy( data, pos, (delta-1) );
                ret_string = data;
	    }
	}
    }
    return ret_string;
}

int etl_xml_node::add_meta_data( etl_string name, etl_string value )
{
    etl_list<etl_string> parts;
    tag.split('>',parts);
    parts.iter_begin();
    if ( !parts.empty() )
    {
        etl_string tagPrefix = *parts.iter_data();
        etl_string tagMeta;
        tagMeta.format((char*)"%s %s=\"%s\">",tagPrefix.c_str(),name.c_str(),value.c_str());
        parts.iter_next();
        if ( parts.iter_valid() )
	{
            etl_string data = *parts.iter_data();
            tagMeta = tagMeta + data;
	}
        tag = tagMeta;
        return 0;
    }
    return -1;
}

etl_hash<etl_string> etl_xml_node::get_meta_data_set()
{
    etl_hash<etl_string> returnVal;

    etl_list<etl_string> mainParts;
    tag.split('>',mainParts);
    mainParts.iter_begin();
    if ( mainParts.empty() )
        return returnVal;

    etl_string main = *mainParts.iter_data();

    etl_list<etl_string> parts;
    main.split(' ', parts);
    for ( parts.iter_begin();parts.iter_valid(); parts.iter_next() )
    {
        etl_list<etl_string> part;
        (parts.iter_data())->split('=', part);
        for ( part.iter_begin(); part.iter_valid();part.iter_next() )
            if ( part.count_elements() == 2 )
            {
                etl_string key = *(part.iter_data());
                part.iter_next();
                etl_string val = *(part.iter_data());
                val.remove('"');
                val.remove('>');
                val.remove('<');
                returnVal.add( key, val );
            }
    }
    return returnVal;
}

etl_string etl_xml_node::get_data()
{
    etl_string null_data;
    etl_string tag_tmp = tag;
    etl_list<etl_string> parts;
    tag_tmp.split('>', parts);

    parts.iter_begin();
    parts.iter_next();

    if ( parts.iter_valid() )
        return *parts.iter_data();

    return null_data;
}

int etl_xml_node::set_data( etl_string newData )
{
    etl_list<etl_string> parts;
    tag.split('>',parts);
    parts.iter_begin();
    etl_string tagMeta = *parts.iter_data();
    tagMeta = tagMeta + (char*)">" + newData;
    tag = tagMeta;
    return 0;
}

etl_string etl_xml_node::get_tag_name()
{
    etl_string tag_name = tag;
    int start = 0;
    int end = 0;
    for ( unsigned int i = 0; i<tag.length() ;++i )
    {
        if ( tag_name.c_str()[i] != '<' && tag_name.c_str()[i] != ' ' )
	{
            start = i;
            i = tag.length();
	}
    }
    for ( unsigned int j=start+1; j<tag.length(); ++j )
    {
        if ( tag_name.c_str()[j]==' ' || tag_name.c_str()[j]=='>' )
	{
            end = j;
            j = tag.length();
	}
    }

    return tag_name.sub_string(start,end-start);
}

int etl_xml_node::set_tag_name( etl_string tagName )
{
    tag.format((char*)"<%s>",tagName.c_str());
    return 0;
}

etl_list<etl_xml_node*> etl_xml_node::get_children()
{
    return children;
}

int etl_xml_node::append_child( etl_xml_node* newChild )
{
    return children.append( newChild );
}

etl_xml_node* etl_xml_node::get_parent()
{
    return parent;
}

int etl_xml_node::set_parent( etl_xml_node* newParent )
{
    parent = newParent;
    return 0;
}

etl_xml_node& etl_xml_node::operator = ( const etl_xml_node& obj )
{
    return copy(obj);
}

etl_xml_node& etl_xml_node::copy( const etl_xml_node& obj)
{
    tag      = obj.tag;
    parent   = obj.parent;
    children = obj.children;
    return *this;
}

etl_xml::etl_xml()
    : doc(0), doc_len(0)
{
}

etl_xml::etl_xml(etl_string obj)
    : doc(0), doc_len(0)
{
    set_document(obj.c_str());
}

etl_xml::~etl_xml()
{
    if ( doc )
        free(doc);

    node_set.iter_begin();
    while ( node_set.iter_valid() )
    {
        delete *node_set.iter_data();
        node_set.iter_next();
    }
}

int etl_xml::set_document( const char* xml_document )
{
    if ( NULL != xml_document )
    {
        if ( strlen(xml_document) > 0 ) // make sure there is a document to work with!
	{
            if ( doc )
                free(doc);

            doc = strdup(xml_document);

            if ( NULL != doc )
	    {
                doc_len = (long)strlen(doc);
                return 0;
	    }
	}
    }
    return -1;
}

etl_xml_node* etl_xml::parse()
{
    if ( doc )
    {
        if ( create_tags() == 0 )
	{
            tag_list.iter_begin();

            return build_tree(NULL);
	}
    }
    return NULL;
}

etl_string etl_xml::create_xml_document( etl_xml_node* root )
{
    etl_string doc;
    if ( root )
    {
        etl_string xmlNode = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

    }
    return doc;
}

etl_xml_node* etl_xml::search_for_first(etl_string tag, etl_xml_node* node)
{
    etl_xml_node* found = NULL;
    if ( node != NULL )
    {
        etl_list<etl_xml_node*> kids = node->get_children();
        if ( tag == "" || node == NULL )
            return NULL;
        else if ( tag == (node->get_tag_name()) )
            return node;
        else
            for ( kids.iter_begin(); kids.iter_valid(); kids.iter_next() )
            {
                found = search_for_first( tag, *(kids.iter_data()) );
                if ( found != NULL )
                    break;
            }
    }
    return found;
}

etl_hash<etl_xml_node*> etl_xml::search_for_first_of_any(etl_xml_node* node, char* format, ...)
{
    va_list ap;
    char* s;
    etl_hash<etl_xml_node* > result;
    etl_xml_node* found = NULL;
    if ( node != NULL && format != NULL )
    {
        va_start(ap, format);
        while ( *format )
	{
            switch ( *format++ )
	    {
	    case 's':           
                s = va_arg(ap, char *);
                found = search_for_first(s, node);
                if ( found != NULL )
                    result.add(s, found);
                break;
	    default:
                break;
	    }
	}
        va_end(ap);
    }
    return result;
}
etl_list<etl_xml_node*> etl_xml::query_search_for_all(etl_string tag, etl_xml_node* node, etl_string metaName, etl_string valueDesired)
{
    etl_list<etl_xml_node*> ret = search_for_all(tag,node);
    ret.iter_begin();
    while ( ret.iter_valid() )
        if ( (*(ret.iter_data()))->get_meta_data(metaName) == valueDesired )
            ret.iter_next();
        else
            ret.remove_at_iter();
    return ret;
}
etl_xml_node* etl_xml::query_search_for_first(etl_string tag, etl_xml_node* node, etl_string metaName, etl_string valueDesired)
{
    etl_xml_node* ret = NULL;
    etl_list<etl_xml_node*> all = search_for_all(tag,node);
    for ( all.iter_begin();all.iter_valid();all.iter_next() )
        if ( (*(all.iter_data()))->get_meta_data(metaName) != valueDesired )
            continue;
        else
        {
            ret = (*(all.iter_data()));
            break;
        }
    return ret;
}

etl_hash<etl_list<etl_xml_node*> > etl_xml::search_for_any(etl_xml_node* node, char* format, ...)
{
    va_list ap;
    char* s;
    etl_hash<etl_list<etl_xml_node*> > result;
    etl_list<etl_xml_node*> found;
    if ( node != NULL && format != NULL )
    {
        va_start(ap, format);
        while ( *format )
	{
            switch ( *format++ )
	    {
	    case 's':           
                found.clear();
                s = va_arg(ap, char *);
                found = search_for_all(s, node);
                result.add(s, found); 
                break;
	    default:
                break;
	    }
	}
        va_end(ap);
    }
    return result;
}

etl_list<etl_xml_node*> etl_xml::search_for_all(etl_string tag, etl_xml_node* node)
{
    if ( tag == "" )
        return matchingChildren;
    matchingChildren.clear();
    if ( node != NULL )
    {
        etl_list<etl_xml_node*> kids = node->get_children();
        search_tree( tag, node );
    }
    return matchingChildren;
}

void etl_xml::search_tree(etl_string tag, etl_xml_node* node)
{
    etl_list<etl_xml_node*> kids = node->get_children();
    if ( tag == (node->get_tag_name()) )
        matchingChildren.append(node);
    for ( kids.iter_begin(); kids.iter_valid(); kids.iter_next() )
        search_tree( tag, *(kids.iter_data()) );
}

int etl_xml::print_tree(etl_xml_node* node, int spaces)
{
    for ( int i = 0; i < spaces; i++ )
        printf(" ");
    printf("%s\n", node->tag.c_str());

    node->children.iter_begin();
    while ( node->children.iter_valid() )
    {
        print_tree(*node->children.iter_data(), spaces + 5);
        node->children.iter_next();
    }
    return 0;
}

etl_xml_node* etl_xml::seek(etl_string commaDelimitedPath, etl_xml_node* searchRoot)
{
    etl_list<etl_string> path;
    commaDelimitedPath.split(',', path);
    bool conditionMet = false;
    if ( searchRoot != NULL )
    {
        path.iter_begin();
        if ( path.iter_valid() )
	{
            if ( *(path.iter_data()) == searchRoot->get_tag_name() )
	    {
                path.iter_next();
                if ( !path.iter_valid() )
                    return searchRoot;
                while ( path.iter_valid() )
		{
                    etl_list<etl_xml_node*> children = searchRoot->get_children();
                    conditionMet = false;
                    for ( children.iter_begin();children.iter_valid();children.iter_next() )
		    {
                        if ( *(path.iter_data()) == (*(children.iter_data()))->get_tag_name() )
			{
                            searchRoot = (*(children.iter_data()));
                            conditionMet = true;
                            path.iter_next();
                            break;
			}
		    }
                    if ( !conditionMet )
                        return NULL;
		}
                if ( conditionMet && !path.iter_valid() )
                    return searchRoot;
	    }
	}
    }
    return NULL;
}

int etl_xml::get_document(etl_string& doc, etl_xml_node* parent,bool pretty)
{
    doc += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
    if ( pretty )
        doc+="\n";
    return etl_xml::build_document(doc,parent,((pretty)?2:0));
}

int etl_xml::create_tags()
{
    if ( doc )
    {
        char* begin_tag = doc, * end_tag = doc;
        char* sentry = doc + doc_len;
        bool done = false;

        tag_list.clear();

        while ( !done )
	{
            while ( (begin_tag < sentry) && *begin_tag != '<' )
                begin_tag++;

            end_tag = begin_tag + 1;

            while ( (end_tag < sentry) && *(end_tag + 1) != '<' )
                end_tag++;

            if ( *begin_tag == '<' )
	    {
                long tag_len = (long)((end_tag - begin_tag) + 1);
                char* new_tag = (char*) malloc(tag_len + 1);
                memset(new_tag, 0, tag_len + 1);
                memcpy(new_tag, begin_tag, tag_len);
                etl_string tag(new_tag);

                //if( !is_comment_tag( tag ) )
                tag_list.append(tag);

                free(new_tag);
	    }

            begin_tag = end_tag;

            if ( (begin_tag + 1) >= sentry )
                done = true;
	}
        return 0;
    }
    else
    {
        return -1;
    }
}

bool etl_xml::is_comment_tag( etl_string elem )
{
    return false;
}

etl_xml_node* etl_xml::build_tree(etl_xml_node* parent)
{
    // make sure we have something in the tag_list before we assume the iter_data() call.
    if ( tag_list.empty() )
    {
        return NULL;
    }
  
    etl_string current_tag = *tag_list.iter_data();
  
    if ( *(current_tag.c_str() + 1) == '/' )
    {
        return NULL;
    }
    else
    {
        etl_xml_node* new_node = new etl_xml_node;
        node_set.prepend( new_node );
      
        if ( new_node )
	{
            new_node->parent = parent;
            new_node->tag = current_tag;
	  
            tag_list.iter_next();
	  
            if ( tag_list.iter_valid() )
	    {
                etl_xml_node* new_child;
                while ( tag_list.iter_valid() && (new_child = build_tree(new_node)) )
		{
                    new_node->children.append( new_child );
                    tag_list.iter_next();
		}
	      
                return new_node;
	    }
            else
                return new_node;
	}
    }
    return NULL;
}

int etl_xml::build_document( etl_string& doc,etl_xml_node* parent,int spaces )
{
    if ( parent )
    {
        char* gap = (char*)alloca( spaces + 1 );
        gap[spaces] = 0;
        for ( int i = 0; i < spaces; i++ )
            gap[i] = ' ';
      
        etl_hash<etl_string> nodeMetaSet = parent->get_meta_data_set();
      
        etl_string tag = parent->get_tag_name();
      
        nodeMetaSet.key_list.iter_begin();
        while ( nodeMetaSet.key_list.iter_valid() )
	{
            etl_string name = *nodeMetaSet.key_list.iter_data();
            etl_string value = *nodeMetaSet.find(name);
            tag = tag + " " + name + "=" + "\"" + value + "\"";
            nodeMetaSet.key_list.iter_next();
	}
      
        etl_string nodeString;
        nodeString.format((char*)"%s<%s>%s%s",((spaces)?gap:""),tag.c_str(),((parent->get_data().c_str())?parent->get_data().c_str():""),((spaces)?"\n":""));
        if ( doc.c_str() )
            doc = doc + nodeString;
        else doc = nodeString;
      
        etl_list<etl_xml_node*> children = parent->get_children();
        children.iter_begin();
        while ( children.iter_valid() )
	{
            build_document( doc, (*children.iter_data()), ((spaces)?spaces+2:0) );
            children.iter_next();
	}
      
        tag = parent->get_tag_name();
      
        nodeString.format((char*)"%s</%s>%s",((spaces)?gap:""),tag.c_str(),((spaces)?"\n":""));
        doc += nodeString;
      
        return 0;
    }
    return -1;
}
